#include <OccluderFrustum.hpp>

namespace Renderer
{
	//
	//
	//
	TOccluderFrustum::TOccluderFrustum(void) : mEnable(false)
	{}

	inline float TOccluderFrustum::PlaneDot(uint index, const Math::Vector3& p) const
	{
		return mMatrixFrustum[index].x * p.x + mMatrixFrustum[index].y * p.y + mMatrixFrustum[index].z * p.z  + mMatrixFrustum[index].w;
	}

	void TOccluderFrustum::Enable(bool value)
	{
		mEnable = value;
	}

	float TOccluderFrustum::GetNearDistance(const Math::AABB& /*aabb*/) const
	{
		return 0.0f;
	}

	bool TOccluderFrustum::AABBInSpace(const Math::AABB& aabb, float& distance) const
	{
		if(!mEnable)
		{
			return true;
		}

		distance = aabb.Distance(Math::Vector3(mInv[3].x, mInv[3].y, mInv[3].z));

		for(uint i=0; i!=4; ++i)
		{
			if(PlaneDot(i, Math::Vector3(aabb.max)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.min.x, aabb.max.y, aabb.max.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.max.x, aabb.min.y, aabb.max.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.min.x, aabb.min.y, aabb.max.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.max.x, aabb.max.y, aabb.min.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.min.x, aabb.max.y, aabb.min.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.max.x, aabb.min.y, aabb.min.z)) > 0.0f) continue;
			if(PlaneDot(i, Math::Vector3(aabb.min)) > 0.0f) continue;
			return false;
		}
		//
		return true;
	}

	void TOccluderFrustum::Update(const Math::Matrix44& proj, const Math::Matrix44& view)
	{
		Math::Matrix44 buildProjection = proj;
		mInv = Math::Inverse(view);

		//float screenDepth = 1;
		//float zMinimum = buildProjection[2][3] / buildProjection[2][2];
		//float r = screenDepth / (screenDepth - zMinimum);
		//buildProjection[2][2] = r;
		//buildProjection[2][3] = -r * zMinimum;

		const Math::Matrix44 modelProjectionMatrix = buildProjection*view;


		Math::Vector4 left;
		left.x = modelProjectionMatrix[0][3] - modelProjectionMatrix[0][0];
		left.y = modelProjectionMatrix[1][3] - modelProjectionMatrix[1][0];
		left.z = modelProjectionMatrix[2][3] - modelProjectionMatrix[2][0];
		left.w = modelProjectionMatrix[3][3] - modelProjectionMatrix[3][0];
		left = Math::Normalize(left);

		Math::Vector4 right;
		right.x = modelProjectionMatrix[0][3] + modelProjectionMatrix[0][0];
		right.y = modelProjectionMatrix[1][3] + modelProjectionMatrix[1][0];
		right.z = modelProjectionMatrix[2][3] + modelProjectionMatrix[2][0];
		right.w = modelProjectionMatrix[3][3] + modelProjectionMatrix[3][0];
		right = Math::Normalize(right);

		Math::Vector4 near;
		near.x = modelProjectionMatrix[0][3] - modelProjectionMatrix[0][1];
		near.y = modelProjectionMatrix[1][3] - modelProjectionMatrix[1][1];
		near.z = modelProjectionMatrix[2][3] - modelProjectionMatrix[2][1];
		near.w = modelProjectionMatrix[3][3] - modelProjectionMatrix[3][1];
		near = Math::Normalize(near);

		Math::Vector4 far;
		far.x = modelProjectionMatrix[0][3] + modelProjectionMatrix[0][1];
		far.y = modelProjectionMatrix[1][3] + modelProjectionMatrix[1][1];
		far.z = modelProjectionMatrix[2][3] + modelProjectionMatrix[2][1];
		far.w = modelProjectionMatrix[3][3] + modelProjectionMatrix[3][1];
		far = Math::Normalize(far);

		mMatrixFrustum = Math::Matrix44(left, right, near, far);
	}
}